package com.tiny.tinyweb.core.utils;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import javax.el.ELContext;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.faces.model.SelectItem;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * JSF utilities.
 *
 * @author ICEsoft Technologies Inc.
 * @since 2.0
 */
public class FacesUtils {

    private final static Logger logger = LoggerFactory.getLogger(FacesUtils.class);

    private static Properties buildProperties = null;

    /**
     * Get servlet context.
     *
     * @return the servlet context
     */
    public static ServletContext getServletContext() {
        return (ServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext();
    }

    public static ExternalContext getExternalContext() {
        FacesContext fc = FacesContext.getCurrentInstance();
        return fc.getExternalContext();
    }

    public static HttpSession getHttpSession(boolean create) {
        return (HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(create);
    }

    /**
     * Get managed bean based on the bean name.
     *
     * @param beanName the bean name
     * @return the managed bean associated with the bean name
     */
    public static Object getJsfManagedBean(String beanName) {
        FacesContext fc = FacesContext.getCurrentInstance();
        ELContext elc = fc.getELContext();
        ExpressionFactory ef = fc.getApplication().getExpressionFactory();
        ValueExpression ve = ef.createValueExpression(elc, getJsfEl(beanName), Object.class);
        try {
            return ve.getValue(elc);
        } catch (Exception e) {
            logger.error("could not get value for bean " + beanName, e);
        }
        return null;
    }

    /**
     * gets the Managed Bean of a given bean name
     *
     * @param beanName the name of the bean
     * @return the managed bean instance or null if name was not found
     */
    public static Object getManagedBean(String beanName) {
        try {
            return FacesContext.getCurrentInstance().getApplication().getELResolver().getValue(
                    FacesContext.getCurrentInstance().getELContext(), null, beanName);
        } catch (Exception e) {
            logger.error("could not get value for bean " + beanName, e);
        }
        return null;
    }

    /**
     * Remove the managed bean based on the bean name.
     *
     * @param beanName the bean name of the managed bean to be removed
     */
    public static void resetJsfManagedBean(String beanName) {
        FacesContext fc = FacesContext.getCurrentInstance();
        ELContext elc = fc.getELContext();
        ExpressionFactory ef = fc.getApplication().getExpressionFactory();
        ef.createValueExpression(elc, getJsfEl(beanName), Object.class).setValue(elc, null);
    }

    /**
     * Remove the managed bean based on the bean name.
     *
     * @param beanName the bean name of the managed bean to be removed
     */
    public static void resetManagedBean(String beanName) {
        if (FacesContext.getCurrentInstance().getViewRoot() != null) {
            FacesContext.getCurrentInstance().getViewRoot().getViewMap().remove(beanName);
        }
    }

    /**
     * Remove the session bean based on the bean name.
     *
     * @param beanName the bean name of the session bean to be removed
     */
    public static void resetSessionBean(String beanName) {
        FacesContext.getCurrentInstance().getExternalContext().getSessionMap().remove(beanName);
    }

    /**
     * Store the managed bean inside the session scope.
     *
     * @param beanName the name of the managed bean to be stored
     * @param managedBean the managed bean to be stored
     */
    public static void setManagedBeanInSession(String beanName, Object managedBean) {
        FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put(beanName, managedBean);
    }

    /**
     * Store the managed bean inside the request scope.
     *
     * @param beanName the name of the managed bean to be stored
     * @param managedBean the managed bean to be stored
     */
    public static void setManagedBeanInRequest(String beanName, Object managedBean) {
        FacesContext.getCurrentInstance().getExternalContext().getRequestMap().put(beanName, managedBean);
    }

    /**
     * Get parameter value from request scope.
     *
     * @param name the name of the parameter
     * @return the parameter value
     */
    public static String getRequestParameter(String name) {
        return FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get(name);
    }

    /**
     * Get named request map object value from request map.
     *
     * @param name the name of the key in map
     * @return the key value if any, null otherwise.
     */
    public static Object getRequestMapValue(String name) {
        return FacesContext.getCurrentInstance().getExternalContext().getRequestMap().get(name);
    }

    /**
     * Gets the action attribute value from the specified event for the given
     * name. Action attributes are specified by &lt;f:attribute /&gt;.
     *
     * @param event
     * @param name
     * @return
     */
    public static String getActionAttribute(ActionEvent event, String name) {
        return (String) event.getComponent().getAttributes().get(name);
    }

    public static String getBuildAttribute(String name) {
        if (buildProperties != null) {
            return buildProperties.getProperty(name, "unknown");
        }
        InputStream is = null;
        try {
            is = getServletContext().getResourceAsStream("/WEB-INF/buildversion.properties");
            buildProperties = new Properties();
            buildProperties.load(is);
        } catch (Throwable e) {
            is = null;
            buildProperties = null;
            return "unknown";
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (Throwable t) {
                }
            }
        }
        return buildProperties.getProperty(name, "unknown");
    }

    /**
     * Gets parameter value from the the session scope.
     *
     * @param name name of the parameter
     * @return the parameter value if any.
     */
    public static String getSessionParameter(String name) {
        FacesContext context = FacesContext.getCurrentInstance();
        HttpServletRequest myRequest = (HttpServletRequest) context.getExternalContext().getRequest();
        myRequest.getSession();
        return myRequest.getParameter(name);
    }

    /**
     * 共SessionMap中获取指定的对象。
     * @param key 对象的Key
     * @return SessionMap中对象
     */
    public static Object getSessionEntry(String key) {
        return FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get(key);
    }

    /**
     * 共SessionMap中获取登录用户的对象。
     * @param key 对象的Key
     * @return SessionMap中登录用户的对象
     */
    public static Object getSessionUser() {
        return getSessionEntry("userInfo");
    }

    public static String getFacesParameter(String parameter) {
        return getFacesParameter(parameter, null);
    }

    /**
     * Get parameter value from the web.xml file
     *
     * @param parameter name to look up
     * @return the value of the parameter
     */
    public static String getFacesParameter(String parameter, String defaultVal) {
        try {
            // Get the servlet context based on the faces context
            ServletContext sc = (ServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext();

            // Return the value read from the parameter
            String toReturn = sc.getInitParameter(parameter);

            return toReturn != null ? toReturn : defaultVal;
        } catch (Exception failedGet) {
        }

        return defaultVal;
    }

    /**
     * Add information message.
     *
     * @param msg the information message
     */
    public static void addInfoMessage(String msg) {
        addInfoMessage(null, msg);
    }

    /**
     * Add information message to a specific client.
     *
     * @param clientId the client id
     * @param msg the information message
     */
    public static void addInfoMessage(String clientId, String msg) {
        FacesContext.getCurrentInstance().addMessage(clientId, new FacesMessage(FacesMessage.SEVERITY_INFO, msg, msg));
    }

    /**
     * Add warn message.
     *
     * @param msg the warn message
     */
    public static void addWarnMessage(String msg) {
        addWarnMessage(null, msg);
    }

    /**
     * Add information message to a specific client.
     *
     * @param clientId the client id
     * @param msg the information message
     */
    public static void addWarnMessage(String clientId, String msg) {
        FacesContext.getCurrentInstance().addMessage(clientId, new FacesMessage(FacesMessage.SEVERITY_WARN, msg, msg));
    }

    /**
     * Add error message.
     *
     * @param msg the error message
     */
    public static void addErrorMessage(String msg) {
        addErrorMessage(null, msg);
    }

    /**
     * Add error message to a specific client.
     *
     * @param clientId the client id
     * @param msg the error message
     */
    public static void addErrorMessage(String clientId, String msg) {
        FacesContext.getCurrentInstance().addMessage(clientId, new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg));
    }

    /**
     * Add fatal message.
     *
     * @param msg the fatal message
     */
    public static void addFatalMessage(String msg) {
        addFatalMessage(null, msg);
    }

    /**
     * Add fatal message to a specific client.
     *
     * @param clientId the client id
     * @param msg the fatal message
     */
    public static void addFatalMessage(String clientId, String msg) {
        FacesContext.getCurrentInstance().addMessage(clientId, new FacesMessage(FacesMessage.SEVERITY_FATAL, msg, msg));
    }

    /**
     * Method to return the value specified via an f:attribute component
     *
     * @param event of the parent that had the f:attribute
     * @param name of the param
     * @return the Object value, which may be null if the attribute wasn't found
     */
    public static Object getFAttribute(ActionEvent event, String name) {
        return event.getComponent().getAttributes().get(name);
    }

    /**
     * Finds component with the given id
     *
     * @param c component check children of.
     * @param id id of component to search for.
     * @return found component if any.
     */
    public static UIComponent findComponent(UIComponent c, String id) {
        if (id.equals(c.getId())) {
            return c;
        }
        Iterator<UIComponent> kids = c.getFacetsAndChildren();
        while (kids.hasNext()) {
            UIComponent found = findComponent(kids.next(), id);
            if (found != null) {
                return found;
            }
        }
        return null;
    }

    /**
     * Finds all component with the given id. Component id's are formed from the
     * concatination of parent component ids. This search will find all componet
     * in the component tree with the specified id as it is possible to have the
     * same id used more then once in the component tree
     *
     * @param root component check children of.
     * @param id id of component to search for.
     * @param foundComponents list of found component with the specified id.
     * @return found component if any.
     */
    public static void findAllComponents(UIComponent root, String id, List<UIComponent> foundComponents) {
        if (id.equals(root.getId())) {
            foundComponents.add(root);
        }
        Iterator<UIComponent> kids = root.getFacetsAndChildren();
        while (kids.hasNext()) {
            findAllComponents(kids.next(), id, foundComponents);
        }
    }

    private static String getJsfEl(String value) {
        return "#{" + value + "}";
    }

    public static boolean isBlank(String check) {
        return ((check == null) || (check.trim().length() == 0));
    }

    public static String join(String[] base) {
        return join(base, ",");
    }

    public static String join(String[] base, String delimiter) {
        // Check if we have enough items to warrant the list logic
        if (base.length > 1) {
            StringBuilder toReturn = new StringBuilder(base.length * 5);

            for (int i = 0; i < base.length; i++) {
                toReturn.append(base[i]);

                // Add a delimiter unless this is the last item
                if ((i + 1) <= (base.length - 1)) {
                    toReturn.append(delimiter);
                }
            }
            return toReturn.toString();
        }
        // return a single item without any delimiters
        else if (base.length == 1) {
            return base[0];
            // if base is empty - return empty string
        } else {
            return "";
        }
    }

    /**
     * Method to refresh the browser to the specified url by adding a
     * META-REFRESH tag to the response
     */
    public static void refreshBrowser(String url) {
        try {
            HttpServletResponse response =
                    (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();

            if (response != null) {
                response.setHeader("Refresh", "0; URL=" + response.encodeRedirectURL(url));
            }
        } catch (Exception ignored) {
        }
    }

    /**
     * Method to redirect the browser to the specified url via the
     * ExternalContext redirect method
     */
    public static void redirectBrowser(String url) {
        try {
            FacesContext context = FacesContext.getCurrentInstance();
            if ((context != null) && (context.getExternalContext() != null)) {
                context.getExternalContext().redirect(url);
            }
        } catch (Exception failedRedirect) {
            failedRedirect.printStackTrace();
        }
    }

    public static String getJSFMessageResourceString(String key) {
        return getJSFMessageResourceString("msgs", key);
    }

    public static String getJSFMessageResourceString(String bundleVar, String key) {
        FacesContext context = FacesContext.getCurrentInstance();
        return context.getApplication().getResourceBundle(context, bundleVar).getString(key);
    }

    /**
     * 从给定的{@link List}中提取Value和Label，构造一个下拉列表用的{@link SelectItem}列表。
     * @param originals 给定的数据列表
     * @param valueName Value列的名称
     * @param labelName Label列的名称
     * @return 下拉列表用的{@link SelectItem}列表
     */
    public static List<SelectItem> getSelectItemsWithZero(List<?> originals, String valueName, String labelName) {
        List<SelectItem> selectItems = new ArrayList<SelectItem>();
        // 第一行显示空行
        selectItems.add(new SelectItem(0, FacesUtils.getJSFMessageResourceString("combbox.black")));
        // 处理条件不足时返回空列表
        if (originals == null ||
                StringUtils.toString(valueName).isEmpty() || StringUtils.toString(labelName).isEmpty()) {
            return selectItems;
        }
        // 用Value列属性的值和Label属性的值来构造下拉列表项
        for (Object original : originals) {
            Object value = ReflectionUtils.getPropertyValue(original, valueName);
            String label = String.valueOf(ReflectionUtils.getPropertyValue(original, labelName));
            selectItems.add(new SelectItem(value, label));
        }
        return selectItems;
    }

    /**
     * 从给定的{@link List}中提取Value和Label，构造一个下拉列表用的{@link SelectItem}列表。
     * @param originals 给定的数据列表
     * @param valueName Value列的名称
     * @param labelName Label列的名称
     * @return 下拉列表用的{@link SelectItem}列表
     */
    public static List<SelectItem> getSelectItemsWithBlank(List<?> originals, String valueName, String labelName) {
        return getSelectItems(originals, valueName, labelName, true, false);
    }

    /**
     * 从给定的{@link List}中提取Value和Label，构造一个下拉列表用的{@link SelectItem}列表。
     * @param originals 给定的数据列表
     * @param valueName Value列的名称
     * @param labelName Label列的名称
     * @return 下拉列表用的{@link SelectItem}列表
     */
    public static List<SelectItem> getSelectItemsShowValue(List<?> originals, String valueName, String labelName) {
        return getSelectItems(originals, valueName, labelName, false, true);
    }

    /**
     * 从给定的{@link List}中提取Value和Label，构造一个下拉列表用的{@link SelectItem}列表。
     * @param originals 给定的数据列表
     * @param valueName Value列的名称
     * @param labelName Label列的名称
     * @return 下拉列表用的{@link SelectItem}列表
     */
    public static List<SelectItem> getSelectItemsShowValueWithBlank(
            List<?> originals, String valueName, String labelName) {
        return getSelectItems(originals, valueName, labelName, true, true);
    }

    /**
     * 从给定的{@link List}中提取Value和Label，构造一个下拉列表用的{@link SelectItem}列表。
     * @param originals 给定的数据列表
     * @param valueName Value列的名称
     * @param labelName Label列的名称
     * @return 下拉列表用的{@link SelectItem}列表
     */
    public static List<SelectItem> getSelectItems(List<?> originals, String valueName, String labelName) {
        return getSelectItems(originals, valueName, labelName, false, false);
    }

    /**
     * 从给定的{@link List}中提取Value和Label，构造一个下拉列表用的{@link SelectItem}列表。
     * @param originals 给定的数据列表
     * @param valueName Value列的名称
     * @param labelName Label列的名称
     * @param withBlack 首行是否需要添加空行
     * @param showValue 是否在Label中显示Value
     * @return 下拉列表用的{@link SelectItem}列表
     */
    public static List<SelectItem> getSelectItems(
            List<?> originals, String valueName, String labelName, boolean withBlack, boolean showValue) {
        List<SelectItem> selectItems = new ArrayList<SelectItem>();
        // 第一行显示空行
        if (withBlack) {
            selectItems.add(new SelectItem("", FacesUtils.getJSFMessageResourceString("combbox.black")));
        }
        // 处理条件不足时返回空列表
        if (originals == null ||
                StringUtils.toString(valueName).isEmpty() || StringUtils.toString(labelName).isEmpty()) {
            return selectItems;
        }
        // 用Value列属性的值和Label属性的值来构造下拉列表项
        for (Object original : originals) {
            Object value = ReflectionUtils.getPropertyValue(original, valueName);
            String label = String.valueOf(ReflectionUtils.getPropertyValue(original, labelName));
            // 用空格连接Value和Label
            if (showValue) {
                label = String.valueOf(value) + " " + label;
            }
            selectItems.add(new SelectItem(value, label));
        }
        return selectItems;
    }

    /**
     * 根据选择值从选择列表中获取对应的显示名。
     * @param selectItems 选择列表
     * @param value 选择值
     * @return 选择值对应的显示名
     */
    public static String getSelectItemsLabel(List<SelectItem> selectItems, String value) {
        if (selectItems != null && selectItems.size() > 0) {
            for (SelectItem selectItem : selectItems) {
                if (value.equals(selectItem.getValue())) {
                    return selectItem.getLabel();
                }
            }
        }
        return "";
    }

    /**
     * Add message to a specific component.
     *
     * @param component the component
     * @param type the message type
     * @param msg the message
     */
    public static void showMessages(UIComponent component, String type, String msg) {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        // remove existing messages
        Iterator<FacesMessage> i = facesContext.getMessages();
        while (i.hasNext()) {
            i.next();
            i.remove();
        }

        // add new messages
        String clientId = component == null ? null : component.getClientId();
        if ("warn".equals(type)) {
            addWarnMessage(clientId, msg);
        } else if ("error".equals(type)) {
            addErrorMessage(clientId, msg);
        } else if ("fatal".equals(type)) {
            addFatalMessage(clientId, msg);
        } else {
            addInfoMessage(clientId, msg);
        }
    }

    /**
     * 从请求中获取客户端IP地址。
     * @param request 请求
     * @return 客户端IP地址
     */
    public static String getIpAddr(HttpServletRequest request) {
        if (request == null) {
            return "unknown";
        }

        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    /**
     * @see javax.faces.context.FacesContext#renderResponse
     */
    public static void renderResponse() {
        FacesContext.getCurrentInstance().renderResponse();
    }

    /**
     * @see javax.faces.context.FacesContext#responseComplete
     */
    public static void responseComplete() {
        FacesContext.getCurrentInstance().responseComplete();
    }

    /**
     * @see javax.faces.context.FacesContext#isPostback
     */
    public static boolean isPostBack() {
        return FacesContext.getCurrentInstance().isPostback();
    }

    public static void resetValue(String id){
        UIViewRoot viewRoot = FacesContext.getCurrentInstance().getViewRoot();
        UIComponent component = viewRoot.findComponent(id);
        if (component instanceof UIInput) {
            UIInput input = (UIInput) component;
            input.setSubmittedValue(null);
            input.setValue(null);
            input.setLocalValueSet(false);
        }
    }

    /**
     * 创建绑定在JSF页面属性的表达式。
     * @param expression 表达式字符串
     * @return 表达式对象
     */
    public static ValueExpression createValueExpression(String expression) {
        return FacesContext.getCurrentInstance().getApplication().getExpressionFactory().createValueExpression(
                FacesContext.getCurrentInstance().getELContext(), expression, Object.class);
    }

    /**
     * 处理页面跳转。
     * @param fromAction 事件表达式
     * @param outcome 跳转页面
     * @see javax.faces.application.NavigationHandler#handleNavigation(FacesContext, String, String)
     */
    public static void navigate(String fromAction, String outcome) {
        FacesContext context = FacesContext.getCurrentInstance();
        context.getApplication().getNavigationHandler().handleNavigation(
                context, fromAction, outcome);
    }

    /**
     * 根据ID获取JSF组件。
     * @param id 组件ID
     * @return JSF组件
     */
    public static UIComponent getComponent(String id) {
        if (FacesContext.getCurrentInstance().getViewRoot() != null) {
            return FacesContext.getCurrentInstance().getViewRoot().findComponent(id);
        }
        return null;
    }
}